package polygon.cli.command;

import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import polygon.cli.Command;
import polygon.cli.CommandException;
import polygon.cli.Layout;
import polygon.cli.util.DownloadUtil;
import polygon.cli.util.ProblemUtil;
import polygon.cli.util.StatementsUtil;
import polygon.util.FileUtil;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;

/**
 * @author MikeMirzayanov (mirzayanovmr@gmail.com)
 */
@SuppressWarnings({"AccessStaticViaInstance"})
public class ArchiveContestCommand extends Command {
    private static final Logger logger = Logger.getLogger(ArchiveContestCommand.class);

    @Override
    public String getDescription() {
        return "(Archive contest(s)): makes contest(s) archives";
    }

    @Override
    public void setupOptions() {
        Option archiveDir = OptionBuilder.withArgName("archiveDir")
                .hasArg()
                .withDescription("Archives directory")
                .create("archiveDir");
        getOptions().addOption(archiveDir);

        Option polygonUrl = OptionBuilder.withArgName("polygonUrl")
                .hasArg()
                .withDescription("Polygon URL (example: http://polygon.server.ru:8080/)")
                .create("polygonUrl");
        getOptions().addOption(polygonUrl);

        Option contests = OptionBuilder.withArgName("contests")
                .hasArg()
                .withDescription("Polygon contest id or several ids (example: \"13\" or \"13,15,16-22\"")
                .create("contests");
        getOptions().addOption(contests);
    }

    @Override
    public void run() {
        Scanner scanner = new Scanner(System.in);

        String polygonUrl = getCommandLine().getOptionValue("polygonUrl");
        if (polygonUrl == null) {
            System.out.println("Enter polygon URL (example: http://polygon.server.ru:8080/c/):");
            polygonUrl = scanner.nextLine();
        }

        String archiveDir = getCommandLine().getOptionValue("archiveDir");
        if (archiveDir == null) {
            System.out.println("Enter archives directory (press ENTER for current directory):");
            archiveDir = scanner.nextLine();
            if (archiveDir.length() == 0) {
                archiveDir = ".";
            }
        }

        String contestsOption = getCommandLine().getOptionValue("contests");
        if (contestsOption == null) {
            System.out.println("Enter contest id or several ids (example: 13 or 13,15,16-22):");
            contestsOption = scanner.nextLine();
        }
        Set<Integer> contests = parseContests(contestsOption);

        if (contests.isEmpty()) {
            throw new CommandException("At least one contest expected.");
        }

        if (contests.size() > 100) {
            throw new CommandException("Can't process more than 100 contests in a single run.");
        }

        if (!polygonUrl.endsWith("/")) {
            polygonUrl += "/";
        }

        for (Integer contest : contests) {
            try {
                process(contest, polygonUrl, archiveDir);
            } catch (IOException e) {
                throw new CommandException("Can't process contest " + contest + '.', e);
            }
        }
    }

    @SuppressWarnings({"ResultOfMethodCallIgnored"})
    private void process(int contest, String polygonUrl, String archiveDir) throws IOException {
        String contestUrl = polygonUrl + "c/" + contest;
        File tempDir = FileUtil.createTemporaryDirectory("ArchiveContestCommand");
        tempDir = new File(tempDir, String.valueOf(contest));
        tempDir.mkdirs();

        try {
            File contestDescriptorFile = new File(tempDir, "contest.xml");
            String contestXml = DownloadUtil.download(new URL(contestUrl + "/contest.xml"), getCli().getLogin(), getCli().getPassword());
            FileUtils.writeStringToFile(contestDescriptorFile, contestXml, "UTF-8");
            logger.info("Contest descriptor for \"" + contest + "\" downloaded");

            NodeList problemsList = FileUtil.extractFromXml(contestDescriptorFile, "/contest/problems/problem", NodeList.class);
            for (int i = 0; i < problemsList.getLength(); i++) {
                Element problem = (Element) problemsList.item(i);

                String letter = problem.getAttribute("index");
                String problemUrl = problem.getAttribute("url");

                File packageDir = ProblemUtil.download(new URL(problemUrl), getCli().getLogin(), getCli().getPassword());
                postprocessDoallSh(new File(packageDir, "doall.sh"));

                FileUtil.renameFile(packageDir, new File(tempDir, problem.getAttribute("name")));

                logger.info("Problem \"" + contest + '-' + letter + ": " + problem.getAttribute("name") + "\" has been downloaded");
            }

            NodeList statementsList = FileUtil.extractFromXml(contestDescriptorFile, "/contest/statements/statement", NodeList.class);
            for (int i = 0; i < statementsList.getLength(); i++) {
                Element statements = (Element) statementsList.item(i);
                String language = statements.getAttribute("language");
                String statementsUrl = contestUrl + '/' + language + "/statements.pdf";
                byte[] bytes = StatementsUtil.download(new URL(statementsUrl), getCli().getLogin(), getCli().getPassword());
                if (bytes.length > 128 && StatementsUtil.looksLikePdf(bytes)) {
                    File statementsFile = new File(tempDir, "problems-" + language.substring(0, 2).toLowerCase() + ".pdf");
                    FileUtil.writeFile(statementsFile, bytes);
                    logger.info("Statements [language=\"" + language + "\"] has been downloaded");
                }
            }

            ProblemUtil.setExecutable(tempDir, ".bat");
            ProblemUtil.setExecutable(tempDir, ".exe");
            ProblemUtil.setExecutable(tempDir, ".sh");

            File targetDir = new File(archiveDir, String.valueOf(contest));
            FileUtil.deleteTotaly(targetDir);
            targetDir.getParentFile().mkdirs();
            FileUtil.renameFile(tempDir, targetDir);
        } finally {
            FileUtil.deleteTotalyAsync(tempDir);
        }
    }

    @SuppressWarnings({"unchecked"})
    private void postprocessDoallSh(File file) throws IOException {
        if (file.isFile()) {
            List<String> lines = FileUtils.readLines(file);

            List<String> testsets = new ArrayList<String>();
            for (String line : lines) {
                if (line.startsWith("mkdir ")) {
                    String[] tokens = line.split(" ");
                    testsets.add(tokens[1].trim());
                }
            }

            for (String testset : testsets) {
                lines.add("");
                lines.add("# dos->unix for " + testset);
                lines.add("for i in `ls " + testset + "/*`; do");
                lines.add("    perl -lne 's/\\r//g; print' $i > $i.unix");
                lines.add("    mv $i.unix $i");
                lines.add("done");
            }

            FileUtils.writeLines(file, lines);
        }
    }

    private Set<Integer> parseContests(String contestsOption) {
        Set<Integer> contests = new TreeSet<Integer>();
        String[] tokens = contestsOption.split("[,;]+");
        for (String token : tokens) {
            String single = token.trim();
            if (!single.isEmpty()) {
                if (single.contains("-")) {
                    String[] items = single.split("-");
                    if (items.length != 2) {
                        throw new CommandException("Illegal argument format: \"" + contestsOption + "\".");
                    } else {
                        int from = Integer.parseInt(items[0]);
                        int to = Integer.parseInt(items[1]);
                        if (from > to) {
                            throw new CommandException("Illegal argument format: \"" + contestsOption + "\".");
                        }
                        for (int i = from; i <= to; i++) {
                            contests.add(i);
                        }
                    }
                } else {
                    contests.add(Integer.parseInt(single));
                }
            }
        }
        return contests;
    }

    private void process(String url, String contestDir, File tempDir,
                         Map<String, String> params, Set<String> problemLetters) {
        if (!processStatements(url, contestDir, "russian", params)) {
            processStatements(url, contestDir, "english", params);
        }

        File contestDescriptorFile = new File(tempDir, "contest.xml");
        try {
            String contestXml = DownloadUtil.download(new URL(url + "/contest.xml"), getCli().getLogin(), getCli().getPassword());
            FileUtils.writeStringToFile(contestDescriptorFile, contestXml, "UTF-8");
        } catch (Exception e) {
            throw new CommandException("Can't download contest descriptor", e);
        }

        try {
            NodeList problemsList = FileUtil.extractFromXml(contestDescriptorFile, "/contest/problems/problem", NodeList.class);
            for (int i = 0; i < problemsList.getLength(); i++) {
                Element problem = (Element) problemsList.item(i);

                String letter = problem.getAttribute("index");
                String problemUrl = problem.getAttribute("url");

                if (!problemLetters.isEmpty() && !problemLetters.contains(letter.toLowerCase())) {
                    continue;
                }

                File packageDir = null;
                try {
                    packageDir = ProblemUtil.download(new URL(problemUrl), getCli().getLogin(), getCli().getPassword());
                    ProblemUtil.ejudgeDeployOrUpdateProblem(packageDir,
                            logger, new File(contestDir), letter.toUpperCase());
                } finally {
                    if (packageDir != null) {
                        try {
                            FileUtil.deleteTotaly(packageDir);
                        } catch (IOException e) {
                            // No operations.
                        }
                    }
                }
                logger.info("Problem \"" + letter + ": " + problem.getAttribute("name") + "\" has been deployed or updated");
            }
        } catch (Exception e) {
            throw new CommandException("Can't process contest or contest problem.", e);
        }
    }

    @SuppressWarnings({"ResultOfMethodCallIgnored"})
    private boolean processStatements(String contestUrl, String contestDir, String language, Map<String, String> params) {
        String layoutStatements = Layout.getContestStatements(params);
        if (layoutStatements.isEmpty()) {
            return false;
        }

        String statementsUrl = contestUrl + '/' + language + "/statements.pdf";
        if (contestUrl.endsWith("/")) {
            statementsUrl = contestUrl + language + "/statements.pdf";
        }

        try {
            byte[] bytes = StatementsUtil.download(new URL(statementsUrl), getCli().getLogin(), getCli().getPassword());
            if (bytes.length > 128 && StatementsUtil.looksLikePdf(bytes)) {
                File statements = new File(contestDir, layoutStatements + ".pdf");
                statements.getParentFile().mkdirs();
                FileUtil.writeFile(statements, bytes);
                logger.info("Statements [language=\"" + language + "\"] has been copied");
            }
            return true;
        } catch (IOException e) {
            logger.error(e);
            return false;
        }
    }
}